/******************************************************************************
 * This program is a 100% Java Email Server.
 ******************************************************************************
 * Copyright (c) 2001-2011, Eric Daugherty (http://www.ericdaugherty.com)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of the copyright holder nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 ******************************************************************************
 * For current versions and more information, please visit:
 * http://javaemailserver.sf.net/
 *
 * or contact the authors at:
 * java@ericdaugherty.com
 * andreaskyrmegalos@hotmail.com
 *
 ******************************************************************************
 * This program is based on the CSRMail project written by Calvin Smith.
 * http://crsemail.sourceforge.net/
 ******************************************************************************
 *
 * $Rev$
 * $Date$
 *
 ******************************************************************************/

package com.ericdaugherty.mail.server.auth;

//Java Imports
import java.io.*;
import javax.security.sasl.SaslException;
import javax.security.sasl.SaslServer;

//Log Imports
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log;

//Encoding Imports
import org.apache.commons.codec.binary.Base64;

//Local Imports
import com.ericdaugherty.mail.server.configuration.ConfigurationManager;
import com.ericdaugherty.mail.server.errors.InvalidAddressException;
import com.ericdaugherty.mail.server.errors.MalformedBase64ContentException;
import com.ericdaugherty.mail.server.info.EmailAddress;
import com.ericdaugherty.mail.server.info.User;
import com.ericdaugherty.mail.server.services.general.DeliveryService;

/**
 * Verify client authentication using SASL PLAIN.
 *
 * @author Andreas Kyrmegalos
 */
public class PlainServerMode implements AuthServerMode, SaslServer{

   /** Logger Category for this class. */
   private static Log log = LogFactory.getLog( PlainServerMode.class );

   /** The ConfigurationManager */
   private final ConfigurationManager configurationManager = ConfigurationManager.getInstance();

   /** The IP address of the client */
   protected String clientIp;

   protected final FinalizeAuthentication finalizeAuthentication;

   protected User user;

   protected boolean domainNeeded, userMBLocked, invalidCredentials;

   public PlainServerMode(boolean isSMTP) {
      if (isSMTP) {
         finalizeAuthentication = new FinalizeAuthenticationSMTP();
      }
      else {
         finalizeAuthentication = new FinalizeAuthenticationPOP3();
      }
   }

   public void setClientIp(String clientIp) {
      this.clientIp = clientIp;
   }

   public byte[] evaluateResponse(byte[] responseBytes) throws SaslException{

      if (!Base64.isArrayByteBase64(responseBytes)) {
         throw new SaslException("Can not decode Base64 Content",new MalformedBase64ContentException());
      }
      String[] usernameandpassword = null;
      try {
         usernameandpassword = new String(Base64.decodeBase64(responseBytes), "UTF-8" ).split("\u0000");
      }
      catch (UnsupportedEncodingException uee) {
         throw new SaslException(uee.getMessage());
      }
      String username, password;
      if (usernameandpassword.length<2) {
         return null;
      }
      else if (usernameandpassword.length == 3) {
         username = usernameandpassword[1];
         password = usernameandpassword[2];
      }
      else {
         username = usernameandpassword[0];
         password = usernameandpassword[1];
      }
      return finalizeAuthentication.finalize(username, password);
   }

   public boolean isDomainNeeded() {
      return domainNeeded;
   }

   public boolean isUserMBLocked() {
      return userMBLocked;
   }

   public boolean isInvalidCredentials() {
      return invalidCredentials;
   }

   protected abstract class FinalizeAuthentication {
      public abstract byte[] finalize(String username, String password) throws SaslException;
   }
   protected class FinalizeAuthenticationSMTP extends FinalizeAuthentication {

      public byte[] finalize(String username, String password) throws SaslException{
         User aUser = null;
         boolean success = false;
         try {
            aUser = configurationManager.getUser(new EmailAddress(username));
            if (aUser!=null&&aUser.isPasswordValid(password)) {
               success = true;
            }
            else {
               success = false;
            }
         }
         catch (InvalidAddressException e) {
            success = false;
         }
         finally {
            aUser = null;
         }
         if (success) {
            if( log.isInfoEnabled() ) log.info( "User: " + username + " logged in successfully.");
            try {
               return "Authentication Successful".getBytes("US-ASCII");
            }
            catch (UnsupportedEncodingException uee) {
               throw new SaslException(uee.getMessage());
            }
         }
         else {
            return null;
         }

      }
   }
   protected class FinalizeAuthenticationPOP3 extends FinalizeAuthentication {

      public byte[] finalize(String username, String password) throws SaslException{
         DeliveryService deliveryService = DeliveryService.getDeliveryService();
         int index = username.indexOf('@');
         if (index==-1) {
            domainNeeded = true;
         }
         else {
            EmailAddress aUserAddress = null;
            try {
               aUserAddress = new EmailAddress(username.substring(0, index), username.substring(index+1));
            }
            catch (InvalidAddressException iae) {
               throw new SaslException(iae.getMessage());
            }
            user = configurationManager.getUser( aUserAddress );
            if (user!=null&&user.isPasswordValid(password)) {
               if( deliveryService.isMailboxLocked( aUserAddress ) ) {
                  userMBLocked = true;
                  user = null;
               }
               else {
                  deliveryService.ipAuthenticated( clientIp );
                  deliveryService.lockMailbox( aUserAddress );
                  if( log.isInfoEnabled() ) log.info( "User: " + username + " logged in successfully.");
                  try {
                     return "Authentication Successful".getBytes("US-ASCII");
                  }
                  catch (UnsupportedEncodingException uee) {
                     throw new SaslException(uee.getMessage());
                  }
               }
            }
            else {
               user = null;
               invalidCredentials = true;
            }
         }
         return null;

      }
   }

   public User getUser() {
      return user;
   }
   
   public void conclude() {
      clientIp = null;
      user = null;
   }

   public String getMechanismName() {
      return "PLAIN";
   }

   public byte[] unwrap(byte[] incoming, int start, int len) throws SaslException {

      return null;
   }

   public byte[] wrap(byte[] outgoing, int start, int len) throws SaslException {

      return null;
   }

   public void dispose() throws SaslException {}

   public Object getNegotiatedProperty(String propName) {

      return null;
   }

   public String getAuthorizationID() {
      return null;
   }

   public boolean isComplete() {
      return true;
   }

}
